// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <string>

#include <rapidjson/document.h>

#include "coin/proto/coin_query.pb.h"
#include "quickfix/Message.h"

namespace coin2::exchange::deribit::order_v2 {

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;
using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;
using coin2::exchange::base::order::OrderContext;

inline std::string GetExchangeOrderErrorCode(const JsonValue& json) {
  if (json.HasMember("error") && json["error"].HasMember("code")) {
    return json["error"]["code"].GetString();
  } else {
    return "";
  }
}

inline std::string GetExchangeOrderErrorMsg(const JsonValue& json) {
  if (json.HasMember("error") && json["error"].HasMember("message")) {
    return json["error"]["message"].GetString();
  } else {
    return "";
  }
}

template <class T>
inline int64_t ToInt64(T&& val) {
  try {
    return stoll(val);
  } catch (std::exception& ex) {
    return 0;
  }
}

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("label")) {
    std::string proc_str = data["label"].GetString();
    if (!proc_str.empty()) {
      try {
        proc_order_id = stoll(proc_str);
      } catch (std::exception& e) {
        LOG(ERROR) << e.what() << proc_str;
      }
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

inline void SetOrderStatus(const std::string& status, ProductOrderElement* order) {
  // what is untriggered?
  if (status == "rejected") {
    order->set_is_live(false);
  } else if (status == "open") {
    order->set_is_live(true);
  } else if (status == "cancelled") {
    order->set_cancel_confirmed(true);
  } else if (status == "filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

inline bool IsDeribitFuturesErrMessage(const JsonValue& doc) {
  // determine whether it is an error msg.
  // the response is in JsonRPC format, the presence of
  // error means there is error.
  return doc.HasMember("error");
}

inline bool IsAccountUpdate(const JsonValue& json) {
  return json.HasMember("params") && json["params"].HasMember("channel") &&
         std::string(json["params"]["channel"].GetString()).find("user.portfolio") !=
             std::string::npos;
}

inline bool IsOrderUpdate(const JsonValue& json) {
  return json.HasMember("params") && json["params"].HasMember("channel") &&
         std::string(json["params"]["channel"].GetString()).find("user.orders") !=
             std::string::npos;
}

inline bool IsFillUpdate(const JsonValue& json) {
  return json.HasMember("params") && json["params"].HasMember("channel") &&
         std::string(json["params"]["channel"].GetString()).find("user.trades") !=
             std::string::npos;
}

inline bool IsChangeUpdate(const JsonValue& json) {
  return json.HasMember("params") && json["params"].HasMember("channel") &&
         std::string(json["params"]["channel"].GetString()).find("user.changes") !=
             std::string::npos;
}

inline std::string GetBase(const std::string& native_symbol) {
  auto pos = native_symbol.find_first_of("-");
  if (pos != std::string::npos) {
    return native_symbol.substr(0, pos);
  } else {
    CHECK(false);
  }
}

class NoFills : public FIX::Group {
 public:
  // 1362: NoFills (NumInGroup)
  // 1363: FillExecID (as as trade_seq in rest/ws message)
  // 1364: FillPx
  // 1365: FillQty
  // 1443: FillLiquidityInd
  NoFills() : FIX::Group(1362, 1363, FIX::message_order(1363, 1364, 1365, 1443, 0)) {}
  FIELD_SET(*this, FIX::FillExecID);
  FIELD_SET(*this, FIX::FillPx);
  FIELD_SET(*this, FIX::FillQty);
  FIELD_SET(*this, FIX::FillLiquidityInd);
};

constexpr auto BUY = '1';
constexpr auto SELL = '2';

constexpr auto MSG_TYPE_SESSION_REJECT = "3";
constexpr auto MSG_TYPE_EXECUTION_REPORT = "8";
constexpr auto MSG_TYPE_ORDER_CANCEL_REJECT = "9";
constexpr auto MSG_TYPE_SECURITY_LIST = "y";

constexpr auto ORDER_STATE_NEW_ORDER = '0';
constexpr auto ORDER_STATE_FILL = '1';
constexpr auto ORDER_STATE_PART_FILL = '2';
constexpr auto ORDER_STATE_CANCELED = '4';
constexpr auto ORDER_STATE_REJECTED = '8';

}  // namespace coin2::exchange::deribit::order_v2
